package com.nchu.commondevelopmentframework.common.storage.impl;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.*;
import com.nchu.commondevelopmentframework.common.exception.file.FileNotExistException;
import com.nchu.commondevelopmentframework.common.exception.file.NullStorageConfigurationException;
import com.nchu.commondevelopmentframework.common.storage.StorageStrategy;
import com.nchu.commondevelopmentframework.common.storage.config.AliCloudOssProperties;
import io.micrometer.common.util.StringUtils;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.text.SimpleDateFormat;

/**
 * @className: AliYunStorageImpl
 * @description: 阿里云存储实现类
 * @author: Li Chuanwei
 * @date: 2024/01/22 22:29
 * @Company: Copyright [日期] by [作者或个人]
 **/
@Component
@Slf4j
public class AliYunStorageImpl implements StorageStrategy {
    @Autowired
    private AliCloudOssProperties aliCloudOssProperties;

    /**
     * 私有构造方法，防止外部实例化
     */
    private AliYunStorageImpl() {}
    /**
     * 添加私有静态成员变量来保存单例实例
     */
    private static OSS ossClientInstance;

    /**
     * 通过这个方法获取实例，使用懒汉模式
     */
    private static synchronized OSS getOSSClientInstance(String accessKey, String secretKey, String endpoint, String bucketName) {
        if (ossClientInstance == null) {
            ossClientInstance = buildOSSClient(accessKey, secretKey, endpoint, bucketName);
        }
        return ossClientInstance;
    }

    private static OSS buildOSSClient(String accessKey, String secretKey, String endpoint, String bucketName){
        //判断配置是否为空
        if(!checkConfig(endpoint, accessKey, secretKey, bucketName)){
            throw new NullStorageConfigurationException("阿里云的endpoint,accessKey,secretKey");
        }
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKey, secretKey);
        if (!bucketExists(ossClient, bucketName)) {
            log.info("Creating bucket " + bucketName);
            createBucket(ossClient, bucketName);
        }
        return ossClient;
    }

    /**
     * 检测配置是否为空
     *
     */
    public static boolean checkConfig(String accessKey, String secretKey, String endpoint, String bucketName) {
        if(StringUtils.isBlank(accessKey) || StringUtils.isBlank(secretKey) || StringUtils.isBlank(endpoint) || StringUtils.isBlank(bucketName)) {
            return false;
        }
        return true;
    }

    /**
     * 生成已上传文件的URL
     *
     */
    private String getObjectUrl(OSS ossClient, String bucketName, String objectName) {
        // 设置URL的过期时间（例如，1小时）
        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000);

        // 生成带签名的URL
        URL url = ossClient.generatePresignedUrl(bucketName, objectName, expiration);

        // 将URL转换为字符串并返回
        return url.toString();
    }

    /**
     * 判断bucket是否存在
     *
     */
    private static boolean bucketExists(OSS ossClient, String bucketName) {
        return ossClient.doesBucketExist(bucketName);
    }

    /**
     * 创建新的bucket
     *
     */
    private static void createBucket(OSS ossClient, String bucketName) {
        // 创建CreateBucketRequest对象。
        CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);

        // 如果创建存储空间的同时需要指定存储类型、存储空间的读写权限、数据容灾类型, 请参考如下代码。
        // 此处以设置存储空间的存储类型为标准存储为例介绍。
        createBucketRequest.setStorageClass(StorageClass.Standard);
        // 数据容灾类型默认为本地冗余存储，即DataRedundancyType.LRS。如果需要设置数据容灾类型为同城冗余存储，请设置为DataRedundancyType.ZRS。
        createBucketRequest.setDataRedundancyType(DataRedundancyType.LRS);
        // 设置存储空间读写权限为公共读，默认为私有。
        createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);

        // 创建存储空间
        ossClient.createBucket(createBucketRequest);
    }

    /**
     * 创建文件夹或目录
     *
     * @param directoryName 目录路径
     * @return true:成功 false:失败
     */
    @Override
    public boolean createDirectory(String directoryName) {
        String bucketName = aliCloudOssProperties.getBucketName();

        //获取OSSClient实例
        OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);

        //判断是否以/结尾
        if(!directoryName.endsWith("/")){
            directoryName += "/";
        }

        try{
            String content = "";

            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, directoryName, new ByteArrayInputStream(content.getBytes()));
            // 上传字符串。
            ossClient.putObject(putObjectRequest);
        } catch (Exception e) {
            log.error("创建文件夹或目录失败", e);
        }
        return false;
    }

    /**
     * 判断文件是否存在
     *
     * @param objectName 文件名（包括路径  eg:222/333.txt）
     * @return 存在返回true，不存在发生异常返回false
     */
    @Override
    public boolean objectExist(String objectName) {
        String bucketName = aliCloudOssProperties.getBucketName();

        //获取OSSClient实例
        OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);


        boolean found = false;
        try {
            // 判断文件是否存在。如果返回值为true，则文件存在，否则存储空间或者文件不存在。
            // 设置是否进行重定向或者镜像回源。默认值为true，表示忽略302重定向和镜像回源；如果设置isINoss为false，则进行302重定向或者镜像回源。
            boolean isINoss = true;
            found = ossClient.doesObjectExist(bucketName, objectName, isINoss);
        }catch (Exception e) {
            log.error("判断文件是否存在失败", e);
            return false;
        }
        return found;
    }

    /**
     * 判断文件夹是否存在【注意是文件夹而不是目录】
     *
     * @param folderName 文件夹名称（结尾为/）
     * @return 存在返回true，不存在发生异常返回false
     */
    @Override
    public boolean folderExist(String folderName) {
        return objectExist(folderName);
    }

    /**
     * 文件上传
     *
     * @param multipartFile 待上传文件
     * @param folderName    目录
     * @param aimFileName   最终保存到存储中的文件名，不需要后缀
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile, String folderName, String aimFileName) {
        String bucketName = aliCloudOssProperties.getBucketName();

        //获取OSSClient实例
        OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);

        //判断文件名是否为空，若为空，则自动UUID生成
        if (!org.springframework.util.StringUtils.hasText(aimFileName)) {
            // Concatenate date, time, and UUID
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
            String dateTime = dateFormat.format(new Date());

            aimFileName = dateTime + "-" + UUID.randomUUID().toString();
        }

        // Get the file extension
        String originalFilename = multipartFile.getOriginalFilename();

        //加入后缀
        if (originalFilename != null && originalFilename.lastIndexOf(".") != -1) {
            String suffix  = originalFilename.substring(originalFilename.lastIndexOf("."));
            aimFileName += suffix;
        }

        // Full file name with path
        String lastFileName = org.springframework.util.StringUtils.hasText(folderName) ?  folderName + "/" + aimFileName : aimFileName;

        try (InputStream inputStream = multipartFile.getInputStream()) {
            // Set metadata for the object (optional)
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(multipartFile.getContentType());

            // Upload the file to OSS
            ossClient.putObject(new PutObjectRequest(bucketName, lastFileName, inputStream, metadata));

            // Generate and return the URL for the uploaded file
            return getObjectUrl(ossClient, bucketName, lastFileName);
        } catch (Exception e) {
            log.error("File upload failed", e);
        }
        return null;
    }

    /**
     * 上传文件【不指定文件夹】
     *
     * @param multipartFile 待上传文件
     * @param fileName      最终保存到存储中的文件名，不需要后缀
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile, String fileName) {
        return uploadFileAndGetURL(multipartFile,null,fileName);
    }

    /**
     * 上传文件【不指定文件夹,不指定目标文件名】
     *
     * @param multipartFile 待上传文件
     * @return 返回上传成功后的文件的url
     */
    @Override
    public String uploadFileAndGetURL(MultipartFile multipartFile) {
        return uploadFileAndGetURL(multipartFile,null,null);
    }

    /**
     * 拷贝文件
     *
     * @param destinationBucketName 目标bucket名称
     * @param destinationKey 目标文件名称
     * @param sourceKey    文件名称（包括目录）
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean copyObject(String destinationBucketName, String destinationKey, String sourceKey) {
        if(objectExist(sourceKey)){
            String bucketName = aliCloudOssProperties.getBucketName();

            //获取OSSClient实例
            OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);

            //检测目标bucket是否存在，如果不存在，则创建
            if(!bucketExists(ossClient, destinationBucketName)){
                createBucket(ossClient, destinationBucketName);
                log.info("Creating bucket " + destinationBucketName);
            }

            try {
                // 创建CopyObjectRequest对象。
                CopyObjectRequest copyObjectRequest = new CopyObjectRequest(bucketName, sourceKey, destinationBucketName, destinationKey);

                // 设置新的文件元信息。
                ObjectMetadata meta = new ObjectMetadata();
                meta.setContentType("text/plain");
                // 指定CopyObject操作时是否覆盖同名目标Object。此处设置为true，表示禁止覆盖同名Object。
                 meta.setHeader("x-oss-forbid-overwrite", "true");

                copyObjectRequest.setNewObjectMetadata(meta);

                // 复制文件。
                CopyObjectResult result = ossClient.copyObject(copyObjectRequest);
                log.info("ETag: " + result.getETag() + " LastModified: " + result.getLastModified());
            }catch (Exception e) {
                log.error("拷贝失败", e);
                return false;
            }
        }
        else {
            throw new FileNotExistException(sourceKey);
        }
        return true;
    }

    /**
     * 文件下载
     *
     * @param fileName 文件名称
     * @param response response
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean getObject(String fileName, HttpServletResponse response) {
        if(objectExist(fileName)){
            String bucketName = aliCloudOssProperties.getBucketName();

            //获取OSSClient实例
            OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);

            try{
                // 构造 GetObjectRequest 对象
                GetObjectRequest getObjectRequest = new GetObjectRequest(bucketName, fileName);

                // 获取文件输入流
                OSSObject ossObject = ossClient.getObject(getObjectRequest);
                InputStream inputStream = ossObject.getObjectContent();

                // 设置响应头
                response.setCharacterEncoding("utf-8");
                response.setContentType("application/octet-stream");
                response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));

                // 获取输出流
                OutputStream outputStream = response.getOutputStream();

                // 复制文件内容到输出流
                byte[] buffer = new byte[1024];
                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }

                // 关闭输入流和输出流
                inputStream.close();
                outputStream.flush();
                outputStream.close();
                log.info("'{}'文件下载成功", fileName);
                return true;
            }catch (Exception e){
                log.error("文件下载失败", e);
                return false;
            }
        }
        else {
            throw new FileNotExistException(fileName);
        }
    }

    /**
     * 获取指定路径下的所有文件对象
     *
     * @param keyPrefix 文件名称
     * @return 存储bucket内指定路径下文件对象信息
     */
    @Override
    public List<String> getBucketObjects(String keyPrefix) {
        String bucketName = aliCloudOssProperties.getBucketName();

        //获取OSSClient实例
        OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);

        List<String> list = new ArrayList<>();
        try {
            // 列举文件。如果不设置keyPrefix，则列举存储空间下的所有文件。如果设置keyPrefix，则列举包含指定前缀的文件。
            ObjectListing objectListing = ossClient.listObjects(bucketName, keyPrefix);
            List<OSSObjectSummary> sums = objectListing.getObjectSummaries();
            for (OSSObjectSummary s : sums) {
                list.add(s.getKey());
            }
        }catch (Exception e){
            log.error("列举文件失败", e);
        }
        return list;
    }

    /**
     * 删除文件
     *
     * @param fileName 文件名
     * @return 成功返回true，失败发生异常返回false
     */
    @Override
    public boolean removeObject(String fileName) {
        if(objectExist(fileName)){
            String bucketName = aliCloudOssProperties.getBucketName();

            //获取OSSClient实例
            OSS ossClient = getOSSClientInstance(aliCloudOssProperties.getAccessKey(), aliCloudOssProperties.getSecretKey(), aliCloudOssProperties.getEndpoint(), bucketName);

            try{
                // 删除文件
                ossClient.deleteObject(bucketName, fileName);
                log.info("'{}'文件删除成功", fileName);
                return true;
            }catch (Exception e){
                log.error("文件删除失败", e);
                return false;
            }
        }
        else {
            throw new FileNotExistException(fileName);
        }
    }
}